import numpy as np
from scipy.stats import norm


class BS:
    """Black Scholes Model
    Example:

    K = 100;  r = 0.1 ;   T = 1 ;   sigma = 0.3 ;   S = np.arange(60,140,0.1)

    Attributes:
        S : current asset price
        K: strike price of the option
        r: risk free rate
        T : time until option expiration
        σ: annualized volatility of the asset's returns
        q: continuously compounded dividend ,0 to ignore dividend

    Returns:
        Call: Call=S0*N(d1)*(e^-qT)-N(d2)K*(e^-rT)
        Put: Put=N(-d2)*K*(e^-rT)-N(-d1)*S0*(e^-qT)
        d1: d1=[ln(S/K)+(r-q+omega^2/2)*T]/[omega*(T^1/2)]
        d2: d2=d1-omega*(T^1/2)

    """

    def __init__(self, S, K, T, r, sigma, q=0):
        self.S = S
        self.K = K
        self.T = T
        self.r = r
        self.sigma = sigma
        self.q = q

    @staticmethod
    def N(x):
        return norm.cdf(x)

    @property
    def params(self):
        return {'S': self.S,
                'K': self.K,
                'T': self.T,
                'r': self.r,
                'q': self.q,
                'sigma': self.sigma}

    def d1(self):
        return (np.log(self.S / self.K) + (self.r - self.q + self.sigma ** 2 / 2) * self.T) \
               / (self.sigma * np.sqrt(self.T))

    def d2(self):
        return self.d1() - self.sigma * np.sqrt(self.T)

    def _call_value(self):
        return self.S * np.exp(-self.q * self.T) * self.N(self.d1()) - \
               self.K * np.exp(-self.r * self.T) * self.N(self.d2())

    def _put_value(self):
        return self.K * np.exp(-self.r * self.T) * self.N(-self.d2()) - \
               self.S * np.exp(-self.q * self.T) * self.N(-self.d1())

    def price(self, type_='C'):
        if type_ == 'C':
            return self._call_value()
        if type_ == 'P':
            return self._put_value()
        else:
            raise ValueError('Unrecognized type')


if __name__ == '__main__':
    K = 100
    r = 0.1
    T = 1
    sigma = 0.3
    S = 100
    print(BS(S, K, T, r, sigma).price('C'))

# Out:
# {'call': 16.73413358238666, 'put': 7.217875385982609}
